/* Copyright (C) 2015-2016 HangZhou Zenzet Technology Co., Ltd.
 * All right reserved

 * File:crypto/locketaes.c
 * Author:guojianchuan/max
 * Date:2016-06-02

 */

/* system header */
#include <string.h>

/* 3rd project header */
#include "openssl/evp.h"
#include "openssl/aes.h"

/* my project header */
#include "locketdef.h"
#include "locketerrno.h"
#include "locketlog.h"
#include "locketcommon.h"
#include "locketconvert.h"

#include "locketaes.h"

/* 以base64计算256位AES，最大字节长度64即可，这里扩容到128字节, 方便日后扩展 */
#define LOCKET_AESKEY_MAX_LEN   128 
#define LOCKET_AESIV_MAX_LEN    128

/**
* @Function:LOCKET_CIPHER_CheckAESParam
* @Author: guojianchuan/max
* @Date: 2016-06-27
* @Description: Check AES param
* @caution: 
*/
static int LOCKET_CIPHER_CheckAESParam (IN CIPHER_AES_S *pstAES)
{
    if (NULL == pstAES)
    {
        return ERR_COMMON_INVALID_PARAM;
    }

    if ((NULL == pstAES->pucKey) ||
        (NULL == pstAES->pucInput) ||
        (NULL == pstAES->pucOutput) ||
        (0 == pstAES->iInputLen) ||
        (0 == strlen((char*)pstAES->pucKey)))
    {
        return ERR_COMMON_INVALID_PARAM;
    }

    if (ERR_COMMON_SUCCESS != LOCKET_CIPHER_CheckEncode (pstAES->iInputEncode))
    {
        return ERR_COMMON_INVALID_PARAM;
    }

    if (ERR_COMMON_SUCCESS != LOCKET_CIPHER_CheckEncode (pstAES->iOutputEncode))
    {
        return ERR_COMMON_INVALID_PARAM;
    }

    return ERR_COMMON_SUCCESS;
}

/**
* @Function:LOCKET_CIPHER_AESCrypt
* @Author: guojianchuan/max
* @Date: 2016-06-02
* @Description: AES encrypt/decrypt
* @caution: Algo format: Algo-bitnum-mode eg:"AES-128-CFB",
*/
int LOCKET_CIPHER_AESCrypt(INOUT CIPHER_AES_S *pstAES)
{
    int iErr = ERR_COMMON_SUCCESS;
    const EVP_CIPHER *pstCipher = NULL;
    /* Check param */
    iErr = LOCKET_CIPHER_CheckAESParam (pstAES);
    if (ERR_COMMON_SUCCESS != iErr)
    {
        LOCKET_LOGERROR ("input param is valid");
        return iErr;
    }

    /* Check algo */
    pstCipher = EVP_get_cipherbyname(pstAES->szAlgoName);
    if (NULL == pstCipher)
    {
        iErr = ERR_COMMON_INVALID_ALGO;
        LOCKET_LOGERROR ("EVP_get_cipherbyname failed. err:%lu %s", iErr, LOCKET_ERR_GetString());
        return iErr;
    }

    /* Convert key and iv (Default base64 to byte) */
    unsigned char szConvertKey[LOCKET_AESKEY_MAX_LEN]  = {0};
    unsigned char szConvertIv[LOCKET_AESIV_MAX_LEN]    = {0};
    unsigned char *pRet                             = NULL;
    int iConvertKeyLen                              = 0;
    int iConvertIvLen                               = 0;
    
    pRet = LOCKET_CIPHER_OtherToByte(ENCODE_BASE64, pstAES->pucKey, strlen ((char*) pstAES->pucKey),
                                       szConvertKey, &iConvertKeyLen);
    if (NULL == pRet)
    {
        return ERR_COMMON_CONVERT_FAILED;   
    }

    if (NULL != pstAES->pucIv)
    {
        pRet = LOCKET_CIPHER_OtherToByte (ENCODE_BASE64, pstAES->pucIv, strlen ((char*) pstAES->pucIv), 
                                          szConvertIv, &iConvertIvLen);
        if (NULL == pRet)
        {
            return ERR_COMMON_CONVERT_FAILED;
        }
    }
    else
    {
        /* ECB without iv */
    }

    /* Convert input */
    unsigned char *pucConvertInput = NULL;
    int iConvertInputLen = 0;
    if (ENCODE_BINARY != pstAES->iInputEncode)
    {
        pucConvertInput = (unsigned char*) LOCKET_malloc (pstAES->iInputLen);
        if (NULL == pucConvertInput)
        {
            iErr = ERR_COMMON_NO_MEM;
            return iErr;
        }
        memset (pucConvertInput, 0, pstAES->iInputLen);

        pRet = LOCKET_CIPHER_OtherToByte (pstAES->iInputEncode, pstAES->pucInput, pstAES->iInputLen, 
                                          pucConvertInput, &iConvertInputLen);
        if (NULL == pRet)
        {
            LOCKET_free ((void**) &pucConvertInput);
            return ERR_COMMON_CONVERT_FAILED;
        }
    }
    else
    {
        pucConvertInput  = pstAES->pucInput;
        iConvertInputLen = pstAES->iInputLen;
    }
    
    /* encrypt/decryt */
    unsigned char *pucConvertOutput = NULL;
    int iConvertOutputLen = 0;	
    EVP_CIPHER_CTX *pstCipherCtx = NULL;
    do
    {
	    if (ENCODE_BINARY != pstAES->iOutputEncode)
	    {
	        /* 对称加密输出的长度和输入是一样的 */
	        pucConvertOutput = (unsigned char*) LOCKET_malloc (iConvertInputLen);
	        if (NULL == pucConvertOutput)
	        {
	            iErr = ERR_COMMON_NO_MEM;
	            break;
	        }
	        memset (pucConvertOutput, 0, iConvertInputLen);
	    }
	    else
	    {
	        pucConvertOutput = pstAES->pucOutput;
	    }
	
        /* init context */
        pstCipherCtx = EVP_CIPHER_CTX_new ();
        if (NULL == pstCipherCtx)
        {
            iErr = ERR_COMMON_NO_MEM;
            break;
        }

        /* init algo */
        iErr = EVP_CipherInit_ex(pstCipherCtx, pstCipher, NULL, szConvertKey, szConvertIv, pstAES->iMode);
        if (ERR_COMMON_SUCCESS != iErr)
        {
            LOCKET_LOGERROR ("EVP_CipherInit_ex failed. %s", LOCKET_ERR_GetString());
            break;
        }
        
        int iOutLen1       = 0;
        
        /* encrypt/decrypt */
        iErr = EVP_CipherUpdate(pstCipherCtx, pucConvertOutput, &iOutLen1, pucConvertInput, iConvertInputLen);
        if (ERR_COMMON_SUCCESS != iErr)
        {
            LOCKET_LOGERROR ("EVP_CipherUpdate failed. %s", LOCKET_ERR_GetString());
            break;
        }
        
        int iOutLen2       = 0;
        iErr = EVP_CipherFinal_ex(pstCipherCtx, pucConvertOutput + iOutLen1, &iOutLen2);
        if (ERR_COMMON_SUCCESS != iErr)
        {
            LOCKET_LOGERROR ("EVP_CipherFinal_ex failed. %s", LOCKET_ERR_GetString());
            break;
        }
        
        if (ENCODE_BINARY != pstAES->iOutputEncode)
        {
            pRet =  LOCKET_CIPHER_ByteToOther (pstAES->iOutputEncode, pucConvertOutput,
                                               iOutLen1 + iOutLen2, pstAES->pucOutput, &pstAES->iOutputLen);
            if (NULL == pRet)
            {
                iErr = ERR_COMMON_CONVERT_FAILED;
                break;
            }
        }
        else
        {
            pstAES->iOutputLen = iOutLen1 + iOutLen2;
        }
    } while (0);

    /* don't forget cleanup */
    EVP_CIPHER_CTX_free (pstCipherCtx);
    if (ENCODE_BINARY != pstAES->iInputEncode)
    {
        LOCKET_free ((void**) &pucConvertInput);
    }
    if (ENCODE_BINARY != pstAES->iOutputEncode)
    {
        LOCKET_free ((void**) &pucConvertOutput);
    }
    return iErr;
}

